# -*- encoding: utf-8 -*-

# Copyright 2010 István Szentandrási
# <etude-music-player@googlegroups.com>

# This file is part of Etude.
#
# Etude is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as 
# published by the Free Software Foundation, either version 3 
# of the License, or (at your option) any later version.
#
# Etude is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Etude. If not, see <http://www.gnu.org/licenses/>.

"""
Collections browser - 2 paned
on the left the collection list with a combo to choose 
between real collections and playlists - that said combo with namespaces
and on the right side the list of songs like in medialib
"""

from . import _, PropertyOrderFrame

import gobject
import gtk
import pango
import xmmsclient

import collection_editor
import etude.client as ec
import etude.config as ecfg
import etude.gui as eg
import etude.plugins as ep
import common

class Collections(ep.PluginPanel):
    """
    Plugin for browsing and editing collections stored on the server
    """

    _type = ep.E_PANEL_TYPE_COLLECTIONS
    _short_name =  "Collections"
    _show_name =  _("Collections")
    _short_desrc = _("Plugin for browsing and editing collections stored on the server.")
    _version = "0.1"
    _unique = "Default_Collections-0.1"
    _title = _("Collections")
    _config = { 'list': ' <title>|<artist>|<album>|<url>|<laststarted>',
            'default_order': '<artist>|<album>|<tracknr>|<title>|<url>'
            }
    #the default order is applied only to the collections, not playlist


    def __init__(self, control):
        ep.PluginPanel.__init__(self, control)
        self.client = control.client
        self.config = control.config
        self.set_shadow_type(gtk.SHADOW_NONE)

        hpaned = gtk.HPaned()
        self.add(hpaned)

        frame1 = gtk.VBox()
        hpaned.pack1(frame1, resize = False, shrink = True)
        
        scroll1 = gtk.ScrolledWindow()
        scroll1.set_policy( gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        scroll1.set_shadow_type(gtk.SHADOW_IN)
        scroll1.set_size_request(150, -1)
        frame1.pack_start(scroll1)

        self.__create_collections_model()

        self.__fill_collections_model()

        self.collview = gtk.TreeView()
        csel = self.collview.get_selection()
        csel.set_mode(gtk.SELECTION_BROWSE)
        self.collview.set_headers_visible(False)
        self.collview.set_model(self.collections_model)
        scroll1.add(self.collview)
        self.collview.connect('button-press-event', self.on_collview_button)

        self.__create_collview_columns()

        frame2 = gtk.VBox()
        scroll2 = gtk.ScrolledWindow()
        scroll2.set_shadow_type(gtk.SHADOW_IN)
        scroll2.set_policy( gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        scroll2.set_size_request(400, -1)
        frame2.pack_start(scroll2)


        hpaned.pack2(frame2, resize=True, shrink=True)
        
        self.songlist = eg.CollectionView(self, format=self.get_config('list'),
                default_order = ecfg.parse_column_xmms(
                    self.get_config('default_order')
                    ),
                sortable=True)
        self.songlist.set_rules_hint(True)
        self.songlist.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
        scroll2.add(self.songlist)

        self.toolbar = gtk.HBox()

        frame2.pack_start(self.toolbar, False, False)
        
        self.open_button = gtk.Button()
        self.open_button.set_image(gtk.image_new_from_stock(gtk.STOCK_OPEN,
            gtk.ICON_SIZE_BUTTON))
        self.open_button.connect('clicked', self.show_open_popup)
        self.open_button.set_tooltip_text(_("Open files/folders/urls"))
        self.toolbar.pack_start(self.open_button, False, False)

        self.edit_button = gtk.Button()
        self.edit_button.set_image(gtk.image_new_from_stock(gtk.STOCK_EDIT,
            gtk.ICON_SIZE_BUTTON))
        self.edit_button.connect('clicked', self.show_edit_popup)
        self.edit_button.set_tooltip_text(_("Edit collection structure"))
        self.toolbar.pack_start(self.edit_button, False, False)
        
        self.add_button = gtk.Button()
        self.add_button.set_image(gtk.image_new_from_stock(gtk.STOCK_ADD,
            gtk.ICON_SIZE_BUTTON))
        self.add_button.connect('clicked', self.show_add_popup)
        self.add_button.set_tooltip_text(_("Add to playlist..."))
        self.toolbar.pack_end(self.add_button, False, False)
        
        self.remove_button = gtk.Button()
        self.remove_button.set_image(gtk.image_new_from_stock(gtk.STOCK_REMOVE,
            gtk.ICON_SIZE_BUTTON))
        self.remove_button.connect('clicked', self.show_rem_popup)
        self.remove_button.set_tooltip_text(_("Remove selected/all"))
        self.toolbar.pack_start(self.remove_button, False, False)

        self.load_button = gtk.Button()
        self.load_button.set_image(gtk.image_new_from_stock(
            gtk.STOCK_GO_FORWARD, gtk.ICON_SIZE_BUTTON))
        self.load_button.connect('clicked', self.show_load_popup)
        self.load_button.set_tooltip_text(_("Set as active playlist"))
        self.toolbar.pack_end(self.load_button, False, False)

        self.show_all()

        #self.toolbar.hide()
        self.songlist.set_sensitive(False)
        self.toolbar.set_sensitive(False)
        
        self.songlist.connect('collection_loaded',
                self.songlist_collection_loaded)
        csel.connect('changed', self.collview_selection_changed)
        self.bind(self.client, 'e_collection_changed', self.collections_changed)
        self.songlist.connect('key_press_event', self.on_list_button_press)
        self.songlist.connect("row-activated", self.song_activated)
    
    def song_activated(self, widget, path, column):
        self.client.playlist.add_id(self.songlist.list_model[path][0])
        self.client.playlist.list_entries(cb=self._song_activated)

    def _song_activated(self, lst):
        """a song was double clicked"""
        self.client.playlist.set_next(len(lst)-1, fast=True, play=True)

    def on_list_button_press(self, widget, event, data = None):
        """
        Add support for Delete on the list of collections
        """
        if event.keyval == gtk.keysyms.Delete:
            if self.remove_button.get_property('visible') == True:
                self.remove_selected(widget, self.songlist.get_coll_name())
        return False

    def __get_namespace(self, name):
        if name == _("Playlists"):
            return "Playlists"
        return "Collections"

    def on_collview_button(self, collview, event):
        """
        Show a context menu for the collection list
        """
        if event.button == 3:
            data = self.collview.get_path_at_pos(int(event.x), int(event.y))
            if data == None:
                return False
            path = data[0]
            if len(path) in (1, 2):
                model = self.collview.get_model()
                menu = gtk.Menu()
                
                if len(path) == 2:
                    value = model[path][1]
                    namespace = self.__get_namespace(model[path[:-1]][1])

                    edit_item = gtk.ImageMenuItem(gtk.STOCK_EDIT)
                    edit_item.connect('activate', self.on_edit_coll, 
                            value, namespace)
                    menu.append(edit_item)
                    rename_item = gtk.MenuItem(label = _("rename").capitalize())
                    rename_item.connect('activate', self.on_rename_coll, 
                            value, namespace)
                    menu.append(rename_item)
                    delete_item = gtk.ImageMenuItem(gtk.STOCK_REMOVE)
                    delete_item.connect('activate', self.on_delete_coll, 
                            value, namespace)
                    menu.append(delete_item)
                    menu.append( gtk.SeparatorMenuItem())
                else:
                    namespace = self.__get_namespace(model[path][1])

                if namespace == "Playlists":
                    new_item = gtk.MenuItem(label = _("New playlist"))
                    new_item.connect('activate', common.on_new_playlist, 
                            None, self.client)
                else:
                    new_item = gtk.MenuItem(label = _("New collection"))
                    new_item.connect('activate', self.on_new_collection)
                menu.append(new_item)
                
                menu.show_all()
                menu.popup(None, None, None, event.button, event.time)
            #return true so the item won't get selected
            #and the collection doesn't get loaded
            return True
        return False

    def on_edit_coll(self, widget, coll_name, namespace):
        def edit_cb(col):
            if ((namespace == 'Collections') or (namespace == 'Playlists' and
                col.__class__ == self.client.collection.PShuffle)):
                dialog = collection_editor.CollectionEditor(coll_name,
                        namespace, col, self, parent=self.get_toplevel())
                dialog.connect('response', self.edit_popup_response)
            else:
                self.show_idlist_edit(col, coll_name, namespace)
            return False
        self.client.collection.get(coll_name, namespace, cb=edit_cb)
        return False

    def on_delete_coll(self, widget, coll_name, namespace):
        """Ask the user if he really want's to delete the collection"""
        mbox = gtk.MessageDialog(
            None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_YES_NO,
            _("You are about to remove collection: ")+coll_name+
            _("\n Are you sure?"))
        res = mbox.run()
        if res == gtk.RESPONSE_YES:
            self.client.collection.remove(coll_name, namespace)
        mbox.destroy()
        return False

    def on_rename_coll(self, widget, coll_name, namespace):
        """
        Show a dialog to rename a callection
        """
        dialog = gtk.MessageDialog( self.get_toplevel(),
                gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                gtk.MESSAGE_QUESTION,
                buttons=gtk.BUTTONS_OK_CANCEL
                )
        dialog.set_markup(_("Please enter the new name:"))
        #inputfield
        entry = gtk.Entry()
        #allow using enter
        entry.set_text(coll_name)
        entry.select_region(0, len(coll_name))
        entry.connect('activate',
                lambda widget: dialog.response(gtk.RESPONSE_OK))
        
        #add entry:
        dialog.vbox.pack_end(entry, True, True)
        dialog.show_all()

        resp = dialog.run()
        if resp == gtk.RESPONSE_OK:
            e_text = entry.get_text()
            if e_text:
                self.client.collection.rename(coll_name, e_text, namespace)
        dialog.destroy()
        return False
         
    def on_new_collection(self, widget):
        """
        Create a new collection using the collection editor
        """
        col = None
        name = ''
        namespace = 'Collections'
        dialog = collection_editor.CollectionEditor(name, namespace, col, self,
                parent=self.get_toplevel())
        dialog.connect('response', self.edit_popup_response)
        dialog.focus_changed(dialog.treeroot)
        return

    def __find_collection(self, name, namespace):
        """
        Find the row on which is the collection on the collection list
        """
        model = self.collections_model
        if namespace == self.__get_namespace(model[(1,)][1]):
            parent_row = model[(1,)]
        else:
            parent_row = model[(0,)]
        iter_childs = parent_row.iterchildren()
        ret_row = None
        try:
            while True:
                row = iter_childs.next()
                row_name = row[1] #the first should be a pixmap
                if row_name == name:
                    ret_row = row
                    raise StopIteration
        except StopIteration:
            pass
        return ret_row

    def collections_changed(self, widget, hash):
        """
        If a collection changed update the list of collections.
        About the collection contents itself the CollectionView will take
        care itself
        """
        if hash[u'type'] == xmmsclient.COLLECTION_CHANGED_UPDATE:
            #if it's just an update return at once
            return False
        model = self.collections_model
        name = hash[u'name']
        type = hash[u'type']
        if type == xmmsclient.COLLECTION_CHANGED_ADD:
            if hash[u'namespace'] == self.__get_namespace(model[(1,)][1]):
                parent_row = model[(1,)]
            else:
                parent_row = model[(0,)]
            model.append(parent_row.iter, (None, name))
        elif type == xmmsclient.COLLECTION_CHANGED_REMOVE:
            row = self.__find_collection(name, hash[u'namespace'])
            if row != None:
                model.remove(row.iter)
        elif type == xmmsclient.COLLECTION_CHANGED_RENAME:
            row = self.__find_collection(name, hash[u'namespace'])
            if row != None:
                row[1] =  hash[u'newname']
        return False
            
    def __menu_position_setter(self, menu, widget):
        """
        Set the position for the popup menus
        """
        x, y = widget.get_toplevel().get_position()
        alloc = widget.get_allocation()
        x += alloc.x + int(alloc.width/2)
        y += alloc.y + int(alloc.height)
        return (x, y, True)

    def show_add_popup(self, widget, data=None):
        """
        show popup for clicking on the add button
        """
        menu = common.create_add_to_playlist_menu(self._add_to_playlist,
                self.client)
        menu.show_all()
        menu.popup(None, None, self.__menu_position_setter, 1, 0, widget)

    def _add_to_playlist(self, widget, playlist, selected):
        """
        Show popup for adding collection contents to a playlist
        """
        if playlist == None:
            playlist = "_active"
        if selected:
            ids = self.songlist.get_selected_ids()
            for song_id in ids:
                self.client.playlist.add_id(song_id, playlist = playlist)
        else:
            self.client.playlist.add_collection(self.songlist.get_collection(),
                        self.songlist.get_order(), playlist)

    def show_load_popup(self, wigdet, data = None):
        """
        Load the selected playlist as active
        """
        name = self.songlist.get_coll_name()
        if len(name)>0:
            self.client.playlist.load(name)

    def show_edit_popup(self, widget, data=None):
        """
        Show the collection editor dialog
        """
        col = self.songlist.get_collection()
        name = self.songlist.coll_name
        namespace = self.songlist.namespace
        if (namespace== "Playlists" 
                and col.__class__ != self.client.collection.PShuffle):
            self.show_idlist_edit(col, name, namespace)
            return False
        dialog = collection_editor.CollectionEditor(name, namespace, col, self,
                parent = self.get_toplevel())
        dialog.connect('response', self.edit_popup_response)
        return
    
    def edit_popup_response(self, dialog, response_id):
        """
        Nonmodal collection editor was closed/clicked save
        """
        if response_id == gtk.RESPONSE_CLOSE:
            dialog.destroy()
            return
        # response_id = RESPONSE_OK
        if response_id == gtk.RESPONSE_OK:
            coll, data = dialog.parse_collection()
            if coll == None:
                merr = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, 
                        gtk.BUTTONS_CLOSE, data[0])
                merr.run()
                merr.destroy()
                dialog.focus_changed(data[1])
                canvas = data[1].get_canvas()
                x, y = canvas.convert_from_item_space(data[1], 0, 0)
                canvas.scroll_to(x, y)
                return
            else:
                name = dialog.coll_name
                namespace = dialog.namespace
                cl = ec.Collection(self.client)
                if name != data and name != '':
                    cl.rename(name, data, namespace)
                cl.save(coll, data, namespace)
                #self.update_if_selected(name, namespace)
        dialog.destroy()
        return
    
    def show_idlist_edit(self, coll, name, namespace):
        """
        Show dialog medialist property editing
        """
        def idlist_edit_response(dlg, response_id):
            if response_id == gtk.RESPONSE_OK:
                coll_name = dlg.check_name()
                if not coll_name:
                    return False
                ret_coll = dialog.get_coll()
                cl = self.client.collection
                if name != coll_name:
                    cl.rename(name, coll_name, namespace)
                cl.save(ret_coll, coll_name, namespace)
                self.update_if_selected(coll_name, namespace)
            dlg.destroy()

        dialog = MediaListDialog(self, coll, name, namespace)
        dialog.connect("response", idlist_edit_response)
        dialog.show()
        return False

    def update_if_selected(self, name, namespace):
        """
        Not sure if this is needed
        """
        sel = self.collview.get_selection()
        model, iter_sel = sel.get_selected()
        if iter_sel == None:
            return
        path = model.get_path(iter_sel)
        sel_name = model.get_value(iter_sel, 1)
        if sel_name == name:
            self.songlist.set_coll_name(name, namespace = namespace)
        return False
    
    def show_rem_popup(self, widget, data = None):
        """
        Show menu for removing entries from the collection
        """

        menu = gtk.Menu()
        item = gtk.MenuItem(label= _("Remove selected"))
        item.connect('activate', self.remove_selected,
                self.songlist.get_coll_name())
        menu.append(item)
        
        item = gtk.MenuItem(label= _("Clear"))
        item.connect('activate', self.clear_all, self.songlist.get_coll_name())
        menu.append(item)

        menu.show_all()
        menu.popup(None, None, self.__menu_position_setter, 1, 0, widget)

    def show_open_popup(self, widget, data = None):
        """
        Show import songs popup
        """

        menu = gtk.Menu()
        item = gtk.MenuItem(label= _("Add files/folders"))
        item.connect('activate', self.filechooser, self.songlist.coll_name)
        menu.append(item)
        
        item = gtk.MenuItem(label= _("Add url"))
        item.connect('activate', self.urlchooser, self.songlist.coll_name)
        menu.append(item)

        menu.show_all()
        menu.popup(None, None, self.__menu_position_setter, 1, 0, widget)

    def remove_selected(self, widget, plist):
        """
        Remove the selected songs from the current playlist/medialist
        """
        namespace = self.songlist.namespace
        if namespace == "Playlists":
            positions = self.songlist.get_selected_positions()
            for pos in positions:
                self.client.playlist.remove_entry(pos, plist)
        elif namespace == "Collections":
            collection = self.songlist.get_collection()
            name = self.songlist.get_coll_name()
            selected_poss = self.songlist.get_selected_positions()
            position_num = len(collection.ids)
            for ind in xrange(len(selected_poss)):
                collection.ids.remove(selected_poss[ind] - ind)
            self.client.collection.save( collection, name, namespace)

    def clear_all(self, widget, plist):
        """
        clear the playlist/medialist
        """
        namespace = self.songlist.namespace
        if namespace == "Playlists":
            self.client.playlist.clear(plist)
        elif namespace == "Collections":
            collection = self.songlist.get_collection()
            name = self.songlist.get_coll_name()
            [collection.ids.remove(0) for song_id in collection.ids]
            self.client.collection.save(collection, name, namespace)

    def filechooser(self, widget, playlist):
        """show a file chooser and add the results"""
        paths = common.filechooser(self.get_toplevel(), self.control)
        for isdir, path in paths:
            if isdir:
                self.client.playlist.radd(path, playlist=playlist)
            else:
                self.client.playlist.add_url(path, playlist=playlist)
        return False

    def urlchooser(self, widget, playlist):
        """show the url chooser"""
        entry_text = common.url_chooser_dialog(self.get_toplevel())
        if entry_text:
            self.client.playlist.add_url(entry_text, playlist=playlist)
        return False
    
    def collview_selection_changed(self, selection, data = None):
        """
        Selection changed on the collection list - load the selected collection
        """
        model, iter_sel = selection.get_selected()
        self.toolbar.set_sensitive(False)
        self.songlist.set_sensitive(False) 
        try:
            path = model.get_path(iter_sel)
            if len(path) == 1:
                raise TypeError
        except TypeError:
            cl = ec.Collection(self.client)
            coll = cl.IDList()
            self.songlist.set_coll(coll)
            return None
        else:
            namespace = self.__get_namespace(model[path[:-1]][1])
            value = model[path][1] #the first one is a pic
            self.songlist.set_coll_name(value, namespace = namespace)
            return False

    def songlist_collection_loaded(self, songlist, coll_name, coll_class):
        """
        After a collection contents were loaded set the show/hide manipulating 
        buttons for the given type of the connection
        """
        namespace = songlist.namespace
        if coll_name == '':
            return False
        #set visible toolbar buttons
        if namespace == "Playlists":
            self.remove_button.show()
            self.open_button.show()
            self.load_button.show()
            self.edit_button.show()
            self.add_button.hide()
            self.songlist.enable_dnd(True, True)
        else: # namespace collections
            cl = ec.Collection(None)
            if coll_class in (cl.PShuffle, cl.Queue, cl.IDList):
                self.remove_button.show()
                self.songlist.enable_dnd(True, True)
            else:
                self.remove_button.hide()
                self.songlist.enable_dnd(src = True, dest = False)
            self.open_button.hide()
            self.edit_button.show()
            self.load_button.hide()
            self.add_button.show()
        self.toolbar.set_sensitive(True)
        self.songlist.set_sensitive(True)
        return False

    def __create_collections_model(self):
        self.collections_model = gtk.TreeStore(gtk.gdk.Pixbuf,
                gobject.TYPE_STRING)
        self.collections_model.set_sort_column_id(1, gtk.SORT_ASCENDING)

    def __fill_collections_model(self):
        self.collections_model.append(None, (None, _("Collections")))
        self.collections_model.append(None, (None, _("Playlists")))
        cl = ec.Collection(self.client)
        cl.list(namespace = "Collections", cb = self.__fill_collections_cb)
        cl.list(namespace = "Playlists", cb = self.__fill_playlists_cb)

    def __fill_collections_cb(self, result, ns = "Collections"):
        if (_(ns) == self.collections_model[(0,)][1]):
            ind = 0
        else:
            ind = 1
        parent = self.collections_model[ind]
        parent_iter = parent.iter
        for collection in result:
            if collection.startswith('_'):
                continue
            self.collections_model.append(parent_iter, (None, collection) )
        self.collview.expand_row(parent.path, True)

    def __fill_playlists_cb(self, result):
        self.__fill_collections_cb(result, ns = "Playlists")

    def __create_collview_columns(self):
        """
        For now the first column is a pixbuf
        """
        #tree columns
        coll = gtk.TreeViewColumn()
        render_pixbuf = gtk.CellRendererPixbuf()
        coll.pack_start(render_pixbuf, expand = False)
        coll.add_attribute(render_pixbuf, 'pixbuf', 0)
        render_text = gtk.CellRendererText()
        coll.pack_start(render_text, expand = True)
        coll.add_attribute(render_text, 'text', 1)
        coll.set_cell_data_func(render_text, self.namespace_highlight)
        self.collview.append_column(coll)
        return False

    def namespace_highlight(self, column, cell, model, row_iter):
        """
        namespaces bold
        """
        path = model.get_path(row_iter)
        if len(path) == 1:
            cell.set_property("weight", pango.WEIGHT_BOLD)
        else:
            cell.set_property("weight", pango.WEIGHT_NORMAL)

    def show_preferences(self, parent):
        """
        Show preferences for collections panel
        """
        dialog = gtk.Dialog(title = _("Collections settings"),
                parent = parent,
                flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CLOSE,
                    gtk.STOCK_APPLY, gtk.RESPONSE_OK)
                )
        dialog.set_default_size(300, 300)
        #inputfield
        entry = gtk.Entry()
        entry.set_text(self.get_config('list'))
        #allow using enter
        entry.connect('activate',
                lambda widget: dialog.response(gtk.RESPONSE_OK))
        entry.set_tooltip_text(common.LIST_STRUCT_TOOLTIP)
        table = gtk.Table(rows = 4, columns = 1)
        table.attach(gtk.Label(_("Columns format")+":"), 0, 1, 0, 1,
                xoptions = 0, yoptions = 0)
        table.attach(entry, 0, 1, 1, 2,
                xoptions = gtk.FILL|gtk.EXPAND, yoptions = 0)
        table.attach(gtk.Label(_("Songs default order")+":"), 0, 1, 2, 3,
                xoptions = 0, yoptions = 0)
        order_frame = PropertyOrderFrame(self.get_config('default_order'))
        table.attach(order_frame, 0, 1, 3, 4)
        
        dialog.vbox.pack_start(table, True, True)
        dialog.show_all()
        res = dialog.run()
        if res == gtk.RESPONSE_OK:
            view_format = entry.get_text()
            order = order_frame.get_selection()
            self.set_config('list', view_format)
            self.set_config('default_order', order)
            self.songlist.set_default_order_format(order)
            self.songlist.set_format(format)
        dialog.destroy()

class MediaListDialog(common.CollectionSaverDialog):

    """
    Dialog for editing medialilsts
    """

    def __init__(self, parent, coll, name, namespace):
        common.CollectionSaverDialog.__init__(self, parent, coll)
        
        self.name_entry.set_text(name)
        if namespace == "Playlists":
            self.ns_combo.set_active(1)
        self.ns_combo.set_sensitive(False)
        self.type_combo.set_sensitive(True)
 
        cl = ec.Collection(None)
        if isinstance(coll, cl.IDList):
            self.type_combo.set_active(0)
        elif isinstance(coll, cl.Queue):
            self.type_combo.set_active(1)
        self.show_all()

